package bpui.editors.matrix;

import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.wat.wcy.isi.mmazur.bp.FacadeProvider;
import org.wat.wcy.isi.mmazur.bp.io.BusinessProcessMatrix;
import org.wat.wcy.isi.mmazur.bp.io.ConditionType;
import org.wat.wcy.isi.mmazur.bp.io.Node;
import org.wat.wcy.isi.mmazur.bp.io.Transition;

import bpui.utils.ModelUtils;
import bpui.utils.RCPUtil;

public class MatrixEditor extends EditorPart {

	public static final String ID = "bpui.editors.MatrixEditor"; //$NON-NLS-1$
	private static final int NAME_LENGTH = 10;
	private MatrixEditorInput input;
	private BusinessProcessMatrix data;

	private final int startx = 5;
	private final int starty = 5;
	private final int tx = 90;
	private final int ty = 40;

	private int selectedRow;
	private int selectedColumn;
	private Image buffer;
	private int height;
	private int width;
	private int numOfElements;

	/**
	 * Create contents of the editor part
	 * 
	 * @param parent
	 */
	@Override
	public void createPartControl(Composite parent) {

		init();

		parent.setLayout(new FillLayout());
		final ScrolledComposite container = new ScrolledComposite(parent,
				SWT.H_SCROLL | SWT.V_SCROLL);
		container.setExpandHorizontal(true);
		container.setExpandVertical(true);
		container.setMinWidth(width);
		container.setMinHeight(height);
		container.setLayout(new FillLayout());

		final Canvas doubleBufferedCanvas = new Canvas(container,
				SWT.NO_BACKGROUND);
		container.setContent(doubleBufferedCanvas);

		Listener mouseListener = new Listener() {

			private String toolTipText = null;

			public void handleEvent(Event event) {
				switch (event.type) {
				case SWT.MouseEnter:
				case SWT.MouseMove:

					if (event.y >= starty && event.y <= starty + ty
							&& event.x > startx + tx
							&& event.x < startx + tx * numOfElements) {
						int i = (event.x - startx) / tx;
						i -= 1;
						Node node = data.getNodesList().get(i);
						StringBuilder strb = new StringBuilder();
						org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType nt = org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType.valueOf(node.getType().name());
						strb.append(nt.getName());
						strb.append("\n");
						if (StringUtils.isNotEmpty(node.getName())) {
							strb.append("\"");
							strb.append(node.getName());
							strb.append("\"");
						}
						toolTipText = strb.toString();
						if (!StringUtils.equals(toolTipText,
								doubleBufferedCanvas.getToolTipText())) {
							doubleBufferedCanvas.setToolTipText(toolTipText);
						}
					} else if (event.y >= starty + ty
							&& event.y <= starty + ty * numOfElements
							&& event.x > startx && event.x < startx + tx) {
						int i = (event.y - starty) / ty;
						i -= 1;
						Node node = data.getNodesList().get(i);
						StringBuilder strb = new StringBuilder();
						org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType nt = org.wat.wcy.isi.mmazur.bp.io.model.enums.NodeType.valueOf(node.getType().name());
						strb.append(nt.getName());
						strb.append("\n");
						if (StringUtils.isNotEmpty(node.getName())) {
							strb.append("\"");
							strb.append(node.getName());
							strb.append("\"");
						}
						toolTipText = strb.toString();
						if (!StringUtils.equals(toolTipText,
								doubleBufferedCanvas.getToolTipText())) {
							doubleBufferedCanvas.setToolTipText(toolTipText);
						}
					} else {
						doubleBufferedCanvas.setToolTipText(null);
					}
					break;
				}
			}
		};
		doubleBufferedCanvas.addListener(SWT.MouseMove, mouseListener);
		doubleBufferedCanvas.addListener(SWT.MouseEnter, mouseListener);
		doubleBufferedCanvas.addMouseListener(new MouseAdapter() {

			@Override
			public void mouseDown(MouseEvent e) {
				int x = (e.x - startx) / tx;
				int y = (e.y - starty) / ty;
				int sr = x > 0 ? x - 1 : -1;
				int sc = y > 0 ? y - 1 : -1;
				if (sr >= 0 && sr < numOfElements - 1 &&
						sc >= 0 && sc < numOfElements - 1) {
					selectedRow = sr;
					selectedColumn = sc;
				}
				doubleBufferedCanvas.redraw();
				super.mouseDown(e);
			}

		});
		doubleBufferedCanvas.addPaintListener(new PaintListener() {

			@Override
			public void paintControl(PaintEvent e) {
				Display display = Display.getCurrent();
				if (display == null || display.isDisposed()) {
					return;
				}
				// Creates new image only absolutely necessary.
				Image buf = (Image) doubleBufferedCanvas
						.getData("double-buffer-image");
				if (buf == null
						|| buf.getBounds().width != doubleBufferedCanvas
								.getSize().x
						|| buf.getBounds().height != doubleBufferedCanvas
								.getSize().y) {
					buf = new Image(display, doubleBufferedCanvas.getSize().x,
							doubleBufferedCanvas.getSize().y);
					doubleBufferedCanvas.setData("double-buffer-image", buf);
				}

				GC gc = new GC(buf);
				try {
					Image img = getImage();
					int r = img.getBounds().width;
					int t = img.getBounds().height;
					gc.fillRectangle(0, 0, r, t);

					Color color1 = display.getSystemColor(SWT.COLOR_GRAY);
					Color color2 = RCPUtil.getColor(255, 255, 255);

					if (selectedRow >= 0 && selectedColumn >= 0) {
						int x = startx + (selectedRow + 1) * tx;
						int y = starty + (selectedColumn + 1) * ty;

						gc.setBackground(color1);

						int dx = startx + tx;
						for (int i = 1; i < numOfElements; i++) {
							gc.fillRectangle(dx, y, tx, ty);
							dx += tx;
						}
						int dy = starty + ty;
						for (int i = 1; i < numOfElements; i++) {
							gc.fillRectangle(x, dy, tx, ty);
							dy += ty;
						}

						gc.setBackground(color2);
						gc.fillRectangle(x, y, tx, ty);
					}

					
					gc.drawImage(img, 0, 0);

					e.gc.drawImage(buf, 0, 0);
				} finally {
					gc.dispose();
				}
			}
		});
	}

	protected Point getCenter(int tx, int ty, int w, int h) {
		int x = (int) (tx / 2) - (int) (w / 2);
		int y = (int) (ty / 2) - (int) (h / 2);
		return new Point(x, y);
	}

	protected String getNodeLabel(int row) {
		Node node = data.getNodesList().get(row);
		return ModelUtils.getNodeLabel(node);
	}

	@Override
	public void setFocus() {
		// Set the focus
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
		// Do the Save operation
	}

	@Override
	public void doSaveAs() {
		// Do the Save As operation
	}

	@Override
	public void dispose() {
		if (buffer != null) {
			buffer.dispose();
		}
		super.dispose();
	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		if (!(input instanceof MatrixEditorInput)) {
			throw new RuntimeException("Wrong input");
		}
		this.input = (MatrixEditorInput) input;
		setSite(site);
		setInput(input);
		setPartName(input.getName());
	}

	@Override
	public boolean isDirty() {
		return false;
	}

	@Override
	public boolean isSaveAsAllowed() {
		return false;
	}

	private Image getImage() {
		if (buffer == null) {
			buffer = drawImage();
		}
		return buffer;
	}

	private void init() {
		String processId = this.input.getProcessId();
		data = FacadeProvider.getBPFacade().getMatrix(processId, false);
		selectedRow = -1;
		selectedColumn = -1;
		numOfElements = data.getNodesList().size() + 1; // with headers
		width = startx + (numOfElements + 1) * tx;
		height = starty + (numOfElements + 1) * ty;
	}

	private Image drawImage() {
		Display display = getDisplay();
		if (data == null) {
			return null;
		}
		int n = numOfElements;
		Image tmpImage = new Image(display, width, height);
		ImageData imageData = tmpImage.getImageData();
		int whitePixel = imageData.palette.getPixel(new RGB(255, 255, 255));
		imageData.transparentPixel = whitePixel;
		imageData.width = width;
		imageData.height = height;
		tmpImage.dispose();
		Image buffer = new Image(display, imageData);
		GC gc = null;
		try {
			gc = new GC(buffer);

			int x = startx;
			int y = starty;
			for (int j = 0; j <= n; j++) {
				int l = j - 1;
				x = startx;
				for (int i = 0; i <= n; i++) {
					int k = i - 1;
					if (i == n && j < n) {
						gc.drawLine(x, y, x, y + ty);
					} else if (j == n && i < n) {
						gc.drawLine(x, y, x + tx, y);
					} else if (i < n && j < n) {
						if ((i == 0 && j > 0) || (j == 0 && i > 0)) { //vertically and horizontally
							Node node = data.getNodesList().get(Math.max(k, l));
							String text = node.getName();
							Image image = ModelUtils.getImage(node.getType());
							Point p = null;
							Point p2 = null;
							if (StringUtils.isNotEmpty(text)) {
								if (text.length() > NAME_LENGTH) {
									text = text.substring(0, 8) + "...";
								} 
								Point strExt = gc.stringExtent(text);
								p = getCenter(tx, ty,
										Math.max(image.getBounds().width, strExt.x),
										image.getBounds().height + strExt.y);
								p2 = getCenter(tx, ty,
										image.getBounds().width,
										image.getBounds().height);
							} else {
								p = getCenter(tx, ty,
										image.getBounds().width,
										image.getBounds().height);
								p2 = getCenter(tx, ty,
										image.getBounds().width,
										image.getBounds().height);
							}
							gc.setBackground(Display.getCurrent()
									.getSystemColor(SWT.COLOR_WHITE));
							gc.fillRectangle(x, y, tx, ty);
							gc.drawImage(image, x + p2.x, y + p.y);
							gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
							gc.drawText(text, x + p.x , y  + p.y + image.getBounds().height);
						} else if (i > 0 && j > 0) {
							Transition tr = data.getNodesMatrix().get(k).getItem().get(l);
							org.wat.wcy.isi.mmazur.bp.io.model.enums.TransitionType tt = org.wat.wcy.isi.mmazur.bp.io.model.enums.TransitionType.valueOf(tr.getType().name());
							String text = tt.getName();
							if (tr.getCondition().getType() != ConditionType.NULL) {
								text += "\n" + tt.getName(); 
							}
							gc.setForeground(Display.getCurrent()
									.getSystemColor(SWT.COLOR_BLACK));
							Point strext = gc.stringExtent(text);
							Point p = getCenter(tx, ty, strext.x, strext.y);
							gc.setBackground(Display.getCurrent()
									.getSystemColor(SWT.COLOR_WHITE));
							gc.drawText(text, x + p.x, y + p.y);
						}
						gc.setBackground(Display.getCurrent().getSystemColor(
								SWT.COLOR_WHITE));
						gc.setForeground(Display.getCurrent().getSystemColor(
								SWT.COLOR_GRAY));
						gc.drawLine(x, y, x + tx, y);
						gc.drawLine(x, y, x, y + ty);
					}
					x += tx;
				}
				y += ty;
			}
			return buffer;
		} catch (Throwable t) {
			t.printStackTrace();
			buffer.dispose();
		} finally {
			gc.dispose();
		}
		return null;
	}

	private Display getDisplay() {
		Display disp = Display.getCurrent();
		if (disp == null) {
			disp = Display.getDefault();
		}
		return disp;
	}
}